﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Xceed.Silverlight.Data;

namespace Xceed.Silverlight.DataGrid
{
  public partial class DataGridControl
  {
    private class RowUpdateManager : UpdateManagerBase
    {
      #region Constructor

      internal RowUpdateManager( DataGridControl dataGridControl )
        : base( dataGridControl )
      {
      }

      #endregion

      #region RootContainer Private Property

      private VirtualRootContainer RootContainer
      {
        get
        {
          return this.DataGridControl.RootContainer;
        }
      }

      #endregion

      #region RootHost Private Property

      private RootHost RootHost
      {
        get
        {
          return this.DataGridControl.RootHost;
        }
      }

      #endregion

      #region Enumerator Private Property

      private IPeekableEnumerator<DataPath> Enumerator
      {
        get
        {
          return this.DataGridControl.m_enumerator;
        }
      }

      #endregion

      protected override Orientation ContainerLayoutOrientation
      {
        get
        {
          return Orientation.Vertical;
        }
      }

      protected override object GetNavigationCurrent()
      {
        ViewReferenceElement? pendingCurrent = this.GetPendingCurrent();

        if( pendingCurrent != null )
        {
          ViewportInfo info = this.GetViewportInfo();
          return info.GetCurrentElementReference(pendingCurrent == ViewReferenceElement.ViewFirst);
        }

        return this.DataGridControl.CurrentDataPath;
      }

      protected override void SetCurrentElement( object element )
      {
        DataPath path = ( DataPath )element;

        if( path != null && path.IsStubPath() )
          throw new DataGridInternalException();

        this.DataGridControl.CurrentDataPath = path;
      }

      protected override bool IsStubElement( object element )
      {
        DataPath path = ( DataPath )element;
        return ( path != null ) && path.IsStubPath();
      }

      protected override int? NavigateCurrent( NavigationOperation navigation, out object newCurrent )
      {
        newCurrent = null;

        ViewReferenceElement? pendingCurrent = this.GetPendingCurrent();

        if( pendingCurrent.HasValue )
        {
          return ( pendingCurrent.Value == ViewReferenceElement.ViewFirst ) ? -1 : 1;
        }

        return this.NavigateInView( this.DataGridControl.CurrentDataPath, navigation, out newCurrent );
      }


      protected override int? NavigateToElement( object element )
      {
        object newCurrent;
        return this.NavigateInView( ( DataPath )element, null, out newCurrent );
      }

      protected override ViewportPosition GetViewportFirstElementPosition()
      {
        ViewportInfo info = this.GetViewportInfo();
        object item = null;
        ILayoutElement firstElement = info.FirstElement;

        if( firstElement != null )
        {
          item = firstElement.ReferenceItem;
        }

        return new ViewportPosition( item, true, new OffsetLength( info.FirstElementOffscreenLength, true ) );
      }

      private int? NavigateInView( DataPath currentPath, NavigationOperation? operation, out object newCurrentElement )
      {
        newCurrentElement = null;

        ViewportInfo info = this.GetViewportInfo();
        List<DataPath> visiblePaths = info.Elements.Select( ( e ) => (DataPath)e.ReferenceItem ).ToList();
        List<DataPath> nonStubPaths = visiblePaths.Where( ( p ) => !this.IsStubElement( p ) ).ToList();

        if( nonStubPaths.Count == 0 )
          return null;

        IComparer<DataPath> comparer = new ComparerDelegate<DataPath>( this.ComparePaths );
        int binaryResult = nonStubPaths.BinarySearch( currentPath, comparer );
        bool beforeFirst = ( binaryResult == ( ~0 ) );
        bool afterLast = ( binaryResult == ( ~nonStubPaths.Count ) );
        bool isFound = ( binaryResult >= 0 );
        int nonStubIndex = ( !isFound ) ? ~binaryResult : binaryResult;

        if( beforeFirst )
        {
          return -1;
        }

        if( afterLast )
        {
          return 1;
        }

        Debug.Assert( nonStubIndex >= 0 && nonStubIndex <= nonStubPaths.Count - 1 );

        DataPath pathFound = nonStubPaths[ nonStubIndex ];

        int visibleIndex = visiblePaths.IndexOf(pathFound);
        DataPath lastVisiblePath = visiblePaths[ visiblePaths.Count - 1 ];
        DataPath firstVisiblePath = visiblePaths[ 0 ];
        bool lastIsEntirelyVisible = (info.LastElementOffscreenLength == 0d);
        bool firstIsEntirelyVisible = (info.FirstElementOffscreenLength == 0d);
        int? lastEntirelyVisibleIndex = null;
        int? firstEntirelyVisibleIndex = null;

        if(lastIsEntirelyVisible)
        {
          lastEntirelyVisibleIndex = visiblePaths.Count - 1;
        } 
        else if(visiblePaths.Count == 2 && firstIsEntirelyVisible)
        {
          lastEntirelyVisibleIndex = 0;
        }
        else if(visiblePaths.Count > 2)
        {
          lastEntirelyVisibleIndex = visiblePaths.Count - 2;
        }

        if(firstIsEntirelyVisible)
        {
          firstEntirelyVisibleIndex = 0;
        } 
        else if(visiblePaths.Count == 2 && lastIsEntirelyVisible)
        {
          firstEntirelyVisibleIndex = visiblePaths.Count - 1;
        }
        else if(visiblePaths.Count > 2)
        {
          firstEntirelyVisibleIndex = 1;
        }

        if(operation != null)
        {
          bool isIncrement = operation.Value.IsIncrement();
          bool isPageOperation = operation.Value.IsPage();

          if( ( isIncrement && this.DataGridControl.IsLastGridItem( currentPath ) )
            || ( !isIncrement && this.DataGridControl.IsFirstGridItem( currentPath ) ) )
          {
            return 0;
          }

          if(!isPageOperation)
          {
            if(isIncrement && isFound)
            {
              visibleIndex += 1;
            }
            else if(!isIncrement)
            {
              visibleIndex--;
            }

            if(visibleIndex < 0)
              return -1;
            else if(visibleIndex == visiblePaths.Count)
              return 1;
          }
          else
          {
            if(isIncrement)
            {
              // PageDown
              // Place current on the last entirely visible item.

              if(!lastEntirelyVisibleIndex.HasValue)
                return 1;

              if(isFound && (visibleIndex == lastEntirelyVisibleIndex) )
                return 1;

              if(visibleIndex > lastEntirelyVisibleIndex)
                return 1;

              visibleIndex = lastEntirelyVisibleIndex.Value;
            }
            else
            {
              // PageUp
              // Place current on the first entirely visible item.

              if(!firstEntirelyVisibleIndex.HasValue)
                return -1;

              if(isFound && (visibleIndex == firstEntirelyVisibleIndex) )
                return -1;

              if(visibleIndex < firstEntirelyVisibleIndex)
                return -1;

              visibleIndex = firstEntirelyVisibleIndex.Value;
            }
          }
        }
        else //operation != null
        {
          if(!lastEntirelyVisibleIndex.HasValue)
          {
            Debug.Assert(!firstEntirelyVisibleIndex.HasValue);
            if(visibleIndex == 0)
              return -1;
            else
              return 1;
          }
          else 
          {
            Debug.Assert(firstEntirelyVisibleIndex.HasValue);
            Debug.Assert(lastEntirelyVisibleIndex.HasValue);

            if(visibleIndex < firstEntirelyVisibleIndex.Value)
              return -1;

            if(visibleIndex > lastEntirelyVisibleIndex.Value)
              return 1;
          }
        }

        newCurrentElement = visiblePaths[ visibleIndex ];

        if( newCurrentElement == null || ( ( DataPath )newCurrentElement ).IsStubPath() )
        {
          newCurrentElement = null;
          return ( operation.Value.IsIncrement() ) ? 1 : -1;
        }
        else
        {
          return 0;
        }
      }

      internal void NavigateToGroup( DataPath startLocation, GroupTarget action )
      {
        if( startLocation.IsStubPath() )
          return;

        switch( action )
        {
          case GroupTarget.Previous:
            this.NavigateToPreviousGroup( startLocation );
            break;

          case GroupTarget.Next:
            this.NavigateToNextGroup( startLocation );
            break;

          default:
            this.NavigateToTargetGroup( startLocation, true, TransitionHint.Update );
            break;
        }
      }

      private void NavigateToPreviousGroup( DataPath currentGroup )
      {
        this.NavigateToTargetGroup( currentGroup, StartPosition.Before, false, false, TransitionHint.ScrollBackward, this.GotoCurrentGroup );
      }

      private void NavigateToNextGroup( DataPath currentGroup )
      {
        this.NavigateToTargetGroup( currentGroup, StartPosition.After, true, false, TransitionHint.ScrollForward, this.OnNavigateToTargetGroupCompleted );
      }

      private void NavigateToTargetGroup( DataPath targetGroup, bool notifyNavigationErrors, TransitionHint transitionHint )
      {
        this.NavigateToTargetGroup( targetGroup, StartPosition.Before, true, notifyNavigationErrors, transitionHint, this.OnNavigateToTargetGroupCompleted );
      }

      private void NavigateToTargetGroup(
        DataPath startLocation,
        StartPosition startPosition,
        bool fetchForward,
        bool notifyNavigationErrors,
        TransitionHint transitionHint,
        AsyncRequestResultHandler asyncResultHandler )
      {
        var dataHelper = this.DataGridControl.DataHelper;
        var targetPath = dataHelper.GetGroupPath( startLocation );
        int fetchCount = ( fetchForward ) ? 1 : -1;

        ReferenceUpdateRequest updateRequest = new ReferenceUpdateRequest( true, targetPath, FetchDirection.Forward );
        updateRequest.UpdateDetails.TransitionHint = transitionHint;
        SortPosition targetPosition = dataHelper.CreateSortPosition( targetPath, startPosition );
        RequestParameters requestParameters;

        if( notifyNavigationErrors )
        {
          requestParameters = new RequestParameters( targetPosition, 0, fetchCount, targetPath, updateRequest );
        }
        else
        {
          requestParameters = new RequestParameters( targetPosition, 0, fetchCount, updateRequest );
        }

        RequestResult requestResult = this.GetElements( requestParameters, asyncResultHandler );
        DataPath firstPath = ( requestResult.FetchedItems != null ) ? requestResult.FetchedItems.FirstOrDefault() : null;

        if( requestResult.EndOfSource || ( ( firstPath != null ) && ( !this.IsStubElement( firstPath ) ) ) )
        {
          asyncResultHandler.Invoke( requestParameters, requestResult );
        }
      }

      private int ComparePaths( DataPath firstPath, DataPath secondPath )
      {
        if( firstPath.IsAncestorOf( secondPath ) )
          return -1;

        if( secondPath.IsAncestorOf( firstPath ) )
          return 1;

        return this.DataGridControl.DataHelper.Compare( firstPath, secondPath );
      }

      private ViewReferenceElement? GetPendingCurrent()
      {
        RequestParameters parameters = null;

        if( m_pendingOperation != null )
        {
          parameters = m_pendingOperation.Parameters;
        }
        else if( m_currentOperation != null )
        {
          parameters = m_currentOperation.Parameters;
        }

        if( parameters != null )
          return parameters.RelatedUpdateRequest.UpdateDetails.CurrentOnCompletion;

        return null;
      }

      protected override void InvalidateMeasure()
      {
        this.RootHost.InvalidateMeasure();
      }

      public void CommitPendingLoad()
      {
        if( m_pendingLoad != null )
        {
          this.Update( m_pendingLoad );
        }
      }

      protected override Size MeasureContainer( Size availableSize, ViewportPosition viewPosition )
      {
        m_availableSize = availableSize;

        this.RootContainer.Layout.SetViewportPosition( viewPosition );
        return this.RootHost.DoMeasure( availableSize );
      }

      protected override ViewportInfo GetViewportInfo()
      {
        return this.RootContainer.GetViewportInfo( new Rect( new Point( 0, 0 ), m_availableSize ) );
      }

      private int GetFetchCount( Size availableSize, bool preloadItems )
      {
        int fetchCount = Convert.ToInt32( Math.Ceiling( availableSize.Height / this.DataGridControl.RowHeight ) ) + 1;
        return ( preloadItems ) ? fetchCount * 2 : fetchCount;
      }

      public override void Update( UpdateRequest newInfo )
      {
        m_pendingLoad = null;
        base.Update( newInfo );
      }

      protected override SourcePositionInfo ApplyReferenceUpdate( Size availableSize, ReferenceUpdateRequest updateRequest )
      {
        UpdateDetails updateDetails = updateRequest.UpdateDetails;
        object referencePoint = updateRequest.FetchPositionReference;
        FetchDirection direction = updateRequest.FetchDirection;
        long skip = updateRequest.Skip;

        long adjustedMoveCount;
        SourcePosition sourcePosition;
        this.ConvertReferencePoint( referencePoint, direction, out sourcePosition, out adjustedMoveCount );
        adjustedMoveCount += skip;

        int fetchCount = this.GetFetchCount( availableSize, updateDetails.RowPreload );
        RequestParameters parameters = new RequestParameters( sourcePosition, adjustedMoveCount, fetchCount, true, updateDetails.ExpandedPath, updateRequest );
        RequestResult fetchResult = this.GetElements( parameters, this.UpdateLayout );
        IEnumerable<DataPath> fetchedItems = fetchResult.FetchedItems;

        if( fetchedItems == null )
        {
          Debug.Assert( fetchResult.EndOfSource );
          fetchedItems = new DataPath[ 0 ];
        }

        bool isAtFirst;
        bool isAtLast;

        if(!fetchResult.EndOfSource)
        {
          isAtFirst = false;
          isAtLast = false;
        }
        else if( fetchResult.ContainAllItems )
        {
          isAtFirst = true;
          isAtLast = true;
        }
        else
        {
          if( fetchResult.FetchedItems == null )
          {
            isAtFirst = fetchResult.Parameters.MoveForward ? false : true;
            isAtLast = !isAtFirst;
          }
          else
          {
            if( fetchResult.Parameters.FetchDirection.IsForward() )
            {

              isAtFirst = false;
              isAtLast = true;

            }
            else
            {
              isAtFirst = true;
              isAtLast = false;
            }
          }
        }

        SourcePositionInfo position = new SourcePositionInfo(
          isAtFirst,
          isAtLast,
          fetchedItems.FirstOrDefault(),
          fetchResult.FirstItemOffset,
          fetchResult.TotalCount );

        if( !fetchResult.Parameters.FetchDirection.IsForward() )
        {
          fetchedItems = fetchedItems.Reverse();
        }

        LayoutUpdateVisitor updateLayoutVisitor = new LayoutUpdateVisitor(
          this.DataGridControl,
          fetchedItems,
          updateDetails.RowAsyncLoadUpdate,
          this.GetLayoutFactory( updateDetails.TransitionHint ) );

        this.RootContainer.Accept( updateLayoutVisitor );

        return position;
      }

      protected override void CommitUpdate()
      {
        this.ApplyToRenderedContainer( ( c ) => c.CommitLayout() );
      }

      protected override void RollbackUpdate()
      {
        this.ApplyToRenderedContainer( 
          ( c ) => 
            {
              LayoutUpdateResult result = c.RollbackLayout();
              foreach( ILayoutElement element in result.AddedElements )
              {
                this.DataGridControl.RootHost.AddElement( element );
              }

              foreach( ILayoutElement element in result.RemovedElements )
              {
                this.DataGridControl.ReleaseElement( element );
              }
            } );
      }

      private void ApplyToRenderedContainer( Action<VirtualContainerBase> action )
      {
        this.RootContainer.Accept(
          new VirtualContainerVisitor( ChildrenPolicy.RenderedChildrenOnly, action ) );
      }

      private bool GetContainAllItems( RequestParameters parameters, RequestResult result )
      {
        return parameters.SourcePosition.IsOutOfBound
          && ( Math.Abs( parameters.ExpectedMoveCount ) == 1 )
          && ( result.EndOfSource );
      }

      protected override void UpdateScrollPosition( SourcePositionInfo newPositionInfo )
      {
        if( this.DataGridControl.m_verticalScrollBar != null )
        {
          this.DataGridControl.m_verticalScrollBar.Scroll -= this.DataGridControl.OnVerticalScroll;

          this.UpdatePositionInformation( this.DataGridControl.m_verticalScrollBar, newPositionInfo );

          this.DataGridControl.UpdateVerticalScrollBarVisibility();

          this.DataGridControl.m_lastVerticalValue = this.DataGridControl.m_verticalScrollBar.Value;
          this.DataGridControl.m_verticalScrollBar.Scroll += this.DataGridControl.OnVerticalScroll;
        }
      }

      private RequestResult GetElements( RequestParameters parameters, AsyncRequestResultHandler asyncResultHandler )
      {
        PeekResult<DataPath> peekResult = this.Peek( parameters );

        if( peekResult.PeekedItems == null )
        {
          parameters.ReversedFetch = true;
          PeekResult<DataPath> reversePeek = this.Peek( parameters );
          parameters.ReversedFetch = ( reversePeek.PeekedItems != null );
          if( parameters.ReversedFetch )
          {
            peekResult = reversePeek;
          }
        }

        JumpMoveAndFetchResult<DataPath> moveAndFetchResult = this.Fetch( parameters, asyncResultHandler );

        return ( moveAndFetchResult != null )
          ? this.ConvertMoveAndFetchResult( parameters, moveAndFetchResult )
          : this.ConvertPeekResult( parameters, peekResult );
      }

      private RequestResult ConvertPeekResult( RequestParameters parameters, PeekResult<DataPath> partialResult )
      {
        int fetchUnit = DataUtils.GetUnit( partialResult.ExpectedPeekCount );
        List<DataPath> peekedItems = ( partialResult.PeekedItems != null )
          ? partialResult.PeekedItems.ToList()
          : new List<DataPath>();

        RelativeOffset firstItemOffset = ( peekedItems.Count > 0 )
          ? partialResult.GetFirstItemOffset()
          : null;

        SourcePosition sourcePosition = partialResult.SourcePosition;
        long initialSkipCount = partialResult.ExpectedSkipCount;
        long stubOffset = ( initialSkipCount + ( peekedItems.Count * fetchUnit ) );
        DataPath parentPath = this.CreateParentTemplatePath( parameters, partialResult );
        bool isSortPosition = ( sourcePosition is SortPosition );

        while( peekedItems.Count < Math.Abs( partialResult.ExpectedPeekCount ) )
        {
          if( isSortPosition && ( stubOffset != initialSkipCount ) )
          {
            if( stubOffset <= 0 && initialSkipCount > 0 )
            {
              stubOffset -= 1;
            }
            else if( stubOffset >= 0 && initialSkipCount < 0 )
            {
              stubOffset += 1;
            }
            initialSkipCount = stubOffset;
          }

          peekedItems.Add( parentPath.CreateChildPath( new StubDataItem( sourcePosition, stubOffset ) ) );
          stubOffset += fetchUnit;
        }

        if( parameters.ReversedFetch )
        {
          if( firstItemOffset != null )
          {
            bool fetchForward = ( fetchUnit == 1 );
            bool offsetForward = ( firstItemOffset.ReferencePosition == ReferencePoint.BeforeFirst );
            int directionFactor = ( fetchForward == offsetForward ) ? 1 : -1;
            long offsetValue = firstItemOffset.EstimateValue.Value + Math.Max( 0, peekedItems.Count - 1 ) * directionFactor;

            // The Last item is at least 1 from the BeforeFirst or AfterLast
            offsetValue = Math.Max( 1, offsetValue );
            firstItemOffset = new RelativeOffset( new EstimateInt64( offsetValue, true ), firstItemOffset.ReferencePosition );
          }
          peekedItems.Reverse();
        }

        return new RequestResult( parameters, false, peekedItems, firstItemOffset, partialResult.TotalCount );
      }

      private RequestResult ConvertMoveAndFetchResult( RequestParameters parameters, JumpMoveAndFetchResult<DataPath> result )
      {
        RequestResult fetchResult;
        if( parameters.ReversedFetch )
        {
          if( !result.Result )
          {
            fetchResult = new RequestResult( parameters, true, null, null, null );
          }
          else
          {
            fetchResult = new RequestResult( parameters, false, result.FetchedItems.Reverse().ToList(), result.EndPositionOffset, result.TotalCount );
          }
        }
        else
        {
          fetchResult = new RequestResult(
            parameters,
            !result.Result,
            result.FetchedItems,
            result.GetFirstItemOffset(),
            result.TotalCount );
        }

        return fetchResult;
      }

      private DataPath CreateParentTemplatePath( RequestParameters parameters, PeekResult<DataPath> partialResult )
      {
        DataPath path;
        int groupLevelCount = this.DataGridControl.GroupDescriptions.Count;
        bool hasPeekedItems = ( partialResult.PeekedItems != null ) 
                           && ( partialResult.PeekedItems.Count > 0 );

        if( parameters.ExpandedPath != null )
        {
          path = parameters.ExpandedPath;

          if( hasPeekedItems )
          {
            DataPath lastPath = partialResult.PeekedItems.Last();
            Debug.Assert( this.DataGridControl.DataHelper.Compare( path, lastPath ) >= 0 );
          }

          while( path.Depth < groupLevelCount )
          {
            path = path.CreateChildPath( new GroupDataItem( null, false ) );
          }

          return path;
        }

        if( hasPeekedItems )
        {
          path = partialResult.PeekedItems.Last();
        }
        else
        {
          if( partialResult.SourcePosition is ItemPosition )
          {
            ItemPosition itemPosition = ( ItemPosition )partialResult.SourcePosition;
            path = itemPosition.Item as DataPath;
            if( path == null )
            {
              if( this.DataGridControl.DataSource.IsPrimaryKeyVolatile )
                throw new InvalidOperationException( "Unable to retrieve the path from the specified item. Either the grid is not bound to a data source or the primary keys have changed." );

              path = this.DataGridControl.DataGridContext.GetDataPathFromItem( itemPosition.Item );
            }
          }
          else if( partialResult.SourcePosition is SortPosition )
          {
            path = DataPath.Empty;
            SortPosition sortPosition = ( SortPosition )partialResult.SourcePosition;
            for( int i = 0; ( i < sortPosition.SortValues.Count - 1 ) && ( i < groupLevelCount ); i++ )
            {
              path = path.CreateChildPath( new GroupDataItem( sortPosition.SortValues[ i ], false ) );
            }
          }
          else
          {
            Debug.Assert( partialResult.SourcePosition.IsOutOfBound );
            path = DataPath.Empty;
          }
        }

        Debug.Assert( path != null );
        path = ( path.Depth > 1 ) ? path.ParentPath : DataPath.Empty;

        //Since created stubs will be fully expanded items, create any missing group levels
        while( path.Depth < groupLevelCount )
        {
          path = path.CreateChildPath( new GroupDataItem( null, false ) );
        }

        return path;
      }

      private void ConvertReferencePoint(
        object referenceObject,
        FetchDirection layoutDirection,
        out SourcePosition sourcePosition,
        out long relativeSkip )
      {
        relativeSkip = 0;

        if( referenceObject == null )
        {
          if( layoutDirection == FetchDirection.Forward )
          {
            sourcePosition = SourcePosition.BeforeFirst;
            relativeSkip = 1;
          }
          else
          {
            sourcePosition = SourcePosition.AfterLast;
            relativeSkip = -1;
          }
        }
        else if( referenceObject is SortPosition )
        {
          sourcePosition = ( SortPosition )referenceObject;
        }
        else
        {
          DataPath referencePath = referenceObject as DataPath;
          Debug.Assert( referencePath != null );
          sourcePosition = null;

          if( referencePath.Depth > 0 )
          {
            StubDataItem stubItem = referencePath.LastChild as StubDataItem;
            if( stubItem != null )
            {
              sourcePosition = stubItem.RelativeSourcePosition;
              relativeSkip = stubItem.Offset;
            }
            else
            {
              int collapsedDepth = this.DataGridControl.DataGridContext.GetCollapsedDepth( referencePath );
              if( collapsedDepth != -1 && collapsedDepth < referencePath.Depth )
              {
                referencePath = referencePath.CreateAncestorPath( collapsedDepth );
              }
            }
          }

          if( sourcePosition == null )
          {
            sourcePosition = new ItemPosition( referencePath );
          }
        }
      }

      private PeekResult<DataPath> Peek( RequestParameters parameters )
      {
        return this.Enumerator.Peek( parameters.ToPeekParameters() );
      }

      private JumpMoveAndFetchResult<DataPath> Fetch(
        RequestParameters parameters,
        AsyncRequestResultHandler asyncResultHandler )
      {
        if( m_currentOperation == null )
          return this.FetchCore( parameters, asyncResultHandler );

        m_pendingOperation = new RequestOperation( parameters, asyncResultHandler );

        return null;
      }

      private JumpMoveAndFetchResult<DataPath> FetchCore(
        RequestParameters parameters,
        AsyncRequestResultHandler asyncResultHandler )
      {
        this.DataGridControl.StartFetchingData( true );

        IPeekableEnumerator<DataPath> enumerator = this.Enumerator;
        bool setCacheSize = parameters.ChangeCacheSize;

        if( setCacheSize )
        {
          ICachingEnumerator cacheEnumerator = enumerator as ICachingEnumerator;
          if( cacheEnumerator != null )
          {
            cacheEnumerator.SetCacheSize( int.MaxValue );
          }
        }

        m_currentOperation = new RequestOperation( parameters, enumerator, asyncResultHandler );
        var asyncResult = enumerator.BeginJumpMoveAndFetch( parameters.ToFetchParameters(), this.FetchCallback, m_currentOperation );

        if( !asyncResult.IsCompleted )
          return null;

        JumpMoveAndFetchResult<DataPath> result = enumerator.EndJumpMoveAndFetch( asyncResult );

        if( setCacheSize )
        {
          this.SetCacheSize( result );
        }

        return result;
      }

      private void SetCacheSize( JumpMoveAndFetchResult<DataPath> result )
      {
        ICachingEnumerator enumerator = this.Enumerator as ICachingEnumerator;
        if( enumerator == null )
          return;

        int newCacheSize = Math.Abs( result.ExpectedFetchCount );
        var items = result.FetchedItems;

        if( ( items != null ) && ( items.Count > 0 ) )
        {
          DataPath targetItem = ( result.ExpectedFetchCount < 0 )
                                  ? items.Last()
                                  : items.First();

          enumerator.SetCacheSize( newCacheSize, new ItemPosition( targetItem ) );
        }
        else
        {
          enumerator.SetCacheSize( newCacheSize );
        }
      }

      private void FetchCallback( IAsyncResult ar )
      {
        RequestOperation operation = ( RequestOperation )ar.AsyncState;
        if( m_currentOperation != operation )
          return;

        m_currentOperation = null;

        if( ar.CompletedSynchronously )
        {
          this.DataGridControl.StartFetchingData( false );

          return;
        }

        IPeekableEnumerator<DataPath> enumerator = operation.Enumerator;
        JumpMoveAndFetchResult<DataPath> fetchResult = enumerator.EndJumpMoveAndFetch( ( JumpMoveAndFetchAsyncResult )ar );

        if( operation.Parameters.ChangeCacheSize )
        {
          this.SetCacheSize( fetchResult );
        }

        if( !object.ReferenceEquals( enumerator, this.Enumerator ) )
        {
          fetchResult = null;
        }

        if( this.HasPendingRequest )
        {
          fetchResult = null;
          m_pendingOperation = null;
        }

        RequestParameters parameters = operation.Parameters;
        AsyncRequestResultHandler asyncResultHandler = operation.AsyncResultHandler;

        if( m_pendingOperation != null )
        {
          parameters = m_pendingOperation.Parameters;
          asyncResultHandler = m_pendingOperation.AsyncResultHandler;

          m_pendingOperation = null;
          fetchResult = this.Fetch( parameters, asyncResultHandler );
        }

        if( ( fetchResult != null ) && ( asyncResultHandler != null ) )
        {
          RequestResult requestResult = this.ConvertMoveAndFetchResult( parameters, fetchResult );

          asyncResultHandler.Invoke( parameters, requestResult );
        }
      }

      private void UpdateLayout( RequestParameters parameters, RequestResult result )
      {
        object positionReference = null;
        if( result.FetchedItems != null )
        {
          positionReference = result.FetchedItems.FirstOrDefault();
        }

        //If the fetch ended out of bounds. Re-display starting at the end/start
        FetchDirection layoutDirection;
        if( positionReference == null )
        {
          layoutDirection = ( parameters.MoveForward ) ? FetchDirection.Backward : FetchDirection.Forward;
        }
        else
        {
          layoutDirection = parameters.FetchDirection;
        }

        ReferenceUpdateRequest updateRequest = new ReferenceUpdateRequest( true, positionReference, layoutDirection );
        updateRequest.UpdateDetails.CurrentOnCompletion = parameters.RelatedUpdateRequest.UpdateDetails.CurrentOnCompletion;
        updateRequest.UpdateDetails.SelectionUpdateToApply = parameters.RelatedUpdateRequest.UpdateDetails.SelectionUpdateToApply;
        updateRequest.UpdateDetails.RowAsyncLoadUpdate = true;
        updateRequest.UpdateDetails.TransitionHint = parameters.RelatedUpdateRequest.UpdateDetails.TransitionHint;

        m_pendingLoad = updateRequest;

        this.InvalidateMeasure();
      }

      private void GotoCurrentGroup( RequestParameters parameters, RequestResult result )
      {
        IEnumerable<DataPath> items = result.FetchedItems;
        DataPath targetPath = ( items != null ) ? items.FirstOrDefault() : null;

        if( targetPath != null )
        {
          this.NavigateToTargetGroup( targetPath
                                    , false
                                    , parameters.RelatedUpdateRequest.UpdateDetails.TransitionHint.HasValue 
                                    ? parameters.RelatedUpdateRequest.UpdateDetails.TransitionHint.Value
                                    : TransitionHint.Update );
        }
        else
        {
          this.OnNavigateToTargetGroupCompleted( parameters, result );
        }
      }

      private void OnNavigateToTargetGroupCompleted( RequestParameters parameters, RequestResult result )
      {
        this.UpdateLayout( parameters, result );

        if( this.HasNavigationError( parameters, result ) )
        {
          var e = new NotificationReceivedEventArgs( NotificationSource.Navigation, NotificationType.NavigationIncomplete );

          this.DataGridControl.RaiseNotificationReceived( e );
        }
      }

      private bool HasNavigationError( RequestParameters parameters, RequestResult result )
      {
        if( result.EndOfSource )
          return true;

        DataPath expectedGroupPath = parameters.ExpectedGroupPath;
        if( expectedGroupPath == null )
          return false;

        var items = result.FetchedItems;
        DataPath targetPath = ( items != null ) ? items.FirstOrDefault() : null;
        if( targetPath == null )
          return true;

        DataPath targetGroupPath = this.DataGridControl.DataHelper.GetGroupPath( targetPath );
        if( expectedGroupPath.Depth > targetGroupPath.Depth )
          return true;

        targetGroupPath = targetGroupPath.CreateAncestorOrSamePath( expectedGroupPath.Depth );

        DataPath expectedParentGroupPath = expectedGroupPath.ParentPath;
        if( ( expectedParentGroupPath != null )
          && ( this.DataGridControl.DataHelper.Compare( targetGroupPath.ParentPath, expectedParentGroupPath ) != 0 ) )
          return true;

        GroupDataItem expectedGroupItem = ( GroupDataItem )expectedGroupPath.LastChild;
        GroupDataItem targetGroupItem = ( GroupDataItem )targetGroupPath.LastChild;
        if( object.Equals( targetGroupItem, expectedGroupItem ) )
          return false;

        object expectedValue = expectedGroupItem.Data;
        object targetValue = targetGroupItem.Data;
        if( !( targetValue is string ) || !( expectedValue is string ) )
          return true;

        return !string.Equals( ( string )targetValue, ( string )expectedValue, StringComparison.CurrentCultureIgnoreCase );
      }

      #region Private Fields

      private Size m_availableSize = new Size( 0, 0 );
      private RequestOperation m_pendingOperation;
      private RequestOperation m_currentOperation;
      private UpdateRequest m_pendingLoad;

      #endregion

      #region AsyncRequestResultHandler Delegate

      private delegate void AsyncRequestResultHandler( RequestParameters parameters, RequestResult result );

      #endregion

      #region RequestParameters Nested Type

      private sealed class RequestParameters
      {
        internal RequestParameters(
          SourcePosition sourcePosition,
          long expectedMoveCount,
          int expectedFetchCount,
          ReferenceUpdateRequest updateRequest )
        {
          m_sourcePosition = sourcePosition;
          m_expectedMoveCount = expectedMoveCount;
          m_expectedFetchCount = expectedFetchCount;
          m_reverseFetch = false;
          m_changeCacheSize = false;
          m_updateRequest = updateRequest;

          if( updateRequest.FetchDirection == FetchDirection.Backward )
          {
            m_expectedFetchCount = -m_expectedFetchCount;
          }
        }

        internal RequestParameters(
          SourcePosition sourcePosition,
          long expectedMoveCount,
          int expectedFetchCount,
          bool changeCacheSize,
          ReferenceUpdateRequest updateRequest )
          : this( sourcePosition, expectedMoveCount, expectedFetchCount, updateRequest )
        {
          m_changeCacheSize = changeCacheSize;
        }

        internal RequestParameters(
          SourcePosition sourcePosition,
          long expectedMoveCount,
          int expectedFetchCount,
          DataPath expectedGroupPath,
          ReferenceUpdateRequest updateRequest )
          : this( sourcePosition, expectedMoveCount, expectedFetchCount, updateRequest )
        {
          m_expectedGroupPath = expectedGroupPath;
        }

        internal RequestParameters(
          SourcePosition sourcePosition,
          long expectedMoveCount,
          int expectedFetchCount,
          bool changeCacheSize,
          DataPath expandedPath,
          ReferenceUpdateRequest updateRequest )
          : this( sourcePosition, expectedMoveCount, expectedFetchCount, changeCacheSize, updateRequest )
        {
          m_expandedPath = expandedPath;
        }

        private long AjustedMoveCount
        {
          get
          {
            if( !m_reverseFetch || ( m_expectedFetchCount == 0 ) )
              return m_expectedMoveCount;

            return m_expectedMoveCount + ( m_expectedFetchCount - DataUtils.GetUnit( m_expectedFetchCount ) );
          }
        }

        private int AjustedFetchCount
        {
          get
          {
            if( !m_reverseFetch || ( this.AjustedMoveCount == m_expectedMoveCount ) )
              return m_expectedFetchCount;

            return -m_expectedFetchCount;
          }
        }

        public SourcePosition SourcePosition
        {
          get
          {
            return m_sourcePosition;
          }
        }

        public FetchDirection FetchDirection
        {
          get
          {
            return ( m_expectedFetchCount >= 0 )
              ? FetchDirection.Forward
              : FetchDirection.Backward;
          }
        }

        public long ExpectedMoveCount
        {
          get
          {
            return m_expectedMoveCount;
          }
        }

        public bool MoveForward
        {
          get
          {
            return ( m_expectedMoveCount > 0 )
                || ( ( m_expectedMoveCount == 0 ) && ( m_expectedFetchCount > 0 ) );
          }
        }

        public bool ChangeCacheSize
        {
          get
          {
            return m_changeCacheSize;
          }
        }

        public bool ReversedFetch
        {
          get
          {
            return m_reverseFetch;
          }
          set
          {
            m_reverseFetch = value;
          }
        }

        public DataPath ExpectedGroupPath
        {
          get
          {
            return m_expectedGroupPath;
          }
        }

        public DataPath ExpandedPath
        {
          get
          {
            return m_expandedPath;
          }
        }

        public ReferenceUpdateRequest RelatedUpdateRequest
        {
          get
          {
            return m_updateRequest;
          }
        }

        public PeekParameters ToPeekParameters()
        {
          return new PeekParameters(
            m_sourcePosition,
            this.AjustedMoveCount,
            this.AjustedFetchCount,
            false );
        }

        public JumpMoveAndFetchParameters ToFetchParameters()
        {
          return new JumpMoveAndFetchParameters(
            m_sourcePosition,
            this.AjustedMoveCount,
            this.AjustedFetchCount );
        }

        private readonly SourcePosition m_sourcePosition;
        private readonly long m_expectedMoveCount;
        private readonly int m_expectedFetchCount;
        private bool m_reverseFetch;
        private readonly bool m_changeCacheSize;
        private readonly DataPath m_expectedGroupPath;
        private readonly DataPath m_expandedPath;
        private readonly ReferenceUpdateRequest m_updateRequest;
      }

      #endregion

      #region RequestOperation Nested Type

      private sealed class RequestOperation
      {
        internal RequestOperation(
          RequestParameters parameters,
          AsyncRequestResultHandler asyncResultHandler )
          : this( parameters, null, asyncResultHandler )
        {
        }

        internal RequestOperation(
          RequestParameters parameters,
          IPeekableEnumerator<DataPath> enumerator,
          AsyncRequestResultHandler asyncResultHandler )
        {
          if( parameters == null )
            throw new ArgumentNullException( "parameters" );

          if( asyncResultHandler == null )
            throw new ArgumentNullException( "asyncResultHandler" );

          m_parameters = parameters;
          m_enumerator = enumerator;
          m_asyncResultHandler = asyncResultHandler;
        }

        internal RequestParameters Parameters
        {
          get
          {
            return m_parameters;
          }
        }

        internal IPeekableEnumerator<DataPath> Enumerator
        {
          get
          {
            return m_enumerator;
          }
        }

        internal AsyncRequestResultHandler AsyncResultHandler
        {
          get
          {
            return m_asyncResultHandler;
          }
        }

        private readonly RequestParameters m_parameters;
        private readonly AsyncRequestResultHandler m_asyncResultHandler;
        private IPeekableEnumerator<DataPath> m_enumerator;
      }

      #endregion

      #region RequestResult Nested Type

      private struct RequestResult
      {
        public RequestResult(
          RequestParameters parameters,
          bool EndOfSource,
          IEnumerable<DataPath> fetchedItems,
          RelativeOffset firstItemPosition,
          EstimateInt64 totalCount )
        {
          if( ( fetchedItems == null || !fetchedItems.Any() )
            && ( firstItemPosition != null ) )
            throw new DataGridInternalException();


          m_parameters = parameters;
          m_endOfSource = EndOfSource;
          m_fetchedItems = fetchedItems;
          m_firstItemOffset = firstItemPosition;
          m_totalCount = totalCount;
        }

        public RequestParameters Parameters { get { return m_parameters; } }
        public bool EndOfSource { get { return m_endOfSource; } }
        public IEnumerable<DataPath> FetchedItems { get { return m_fetchedItems; } }
        public RelativeOffset FirstItemOffset { get { return m_firstItemOffset; } }
        public EstimateInt64 TotalCount { get { return m_totalCount; } }

        public bool ContainAllItems
        {
          get
          {
            bool startAtLimit =
              ( m_parameters.SourcePosition.IsBeforeFirst && ( m_parameters.ExpectedMoveCount == 1 ) )
              || ( m_parameters.SourcePosition.IsAfterLast && ( m_parameters.ExpectedMoveCount == -1 ) );

            return startAtLimit && ( this.EndOfSource );
          }
        }

        private RequestParameters m_parameters;
        private bool m_endOfSource;
        private IEnumerable<DataPath> m_fetchedItems;
        private RelativeOffset m_firstItemOffset;
        private EstimateInt64 m_totalCount;
      }

      #endregion
    }

    internal enum NavigationOperation
    {
      PreviousPage,
      PreviousItem,
      NextPage,
      NextItem
    };
  }
}
